<?php

namespace W3;

/**
 * 框架路由类
 *
 * @author edikud
 * @date 2022/10/22
 * @copyright Copyright (c) 2022 W3 (http://www.mcooo.com)
 * @license GNU General Public License 2.0
 */

class Router
{
    /**
     * 路径前缀
     * @var string
     */
    protected static $prefix;
	
    /**
     * 当前路由索引/标志
     * @var array
     */
    protected static $current;
	
    /**
     * 路由映射数组
     * @var array
     */
    protected static $rules = [];
	
    /**
     * 静态路由数组
     * @var array
     */
    protected static $staticRoutes;
	
    /**
     * 回调
     * @var array
     */
    protected static $callbacks = [];
	
    /**
     * 允许的方法列表
     * @var array
     */
    protected static $allowedMethods = [
        'ANY', 'GET', 'POST', 'PATCH', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'
    ];

    /**
     * 导入解析后的路由规则
     *
     * @access public
     * @param mixed $rules 配置信息
     * @return void
     */
    public static function allowedMethods(): array
    {
        return static::$allowedMethods;
    }

    /**
     * 注册路由规则 (URL模式映射)
     *        
     * e.g:
	 * search GET|POST|PUT|DELETE /type/{type:in{index|add|edit|delete}|level{9}}
	 * GET|POST|PUT|DELETE /type-{type:in{index|add|edit|delete}}
	 * search /search/{keywords?}
	 * GET /search/{keywords}?}	 
	 * /search/{keywords:xss}/{page:num?}.html
	 *
     * @param string $pattern: search GET|POST|PUT|DELETE /search/@keywords 路由标识 请求类型 路由规则    
     * @param string|null $widget 组件名称
	 * @param string|null $action 组件动作
     * @return void
     */
    public static function map($pattern, ?string $widget = null, ?string $action = null, ?string $after = null, array $vars = [], array $request = [])
    {
		if (is_array($pattern)) {
            foreach ($pattern as $arr)
		    {
			    static::map(...$arr);
            }
			return;
		}
		
		$patterns = explode(' ', trim($pattern), 3);
		$count = count($patterns);
		
        switch ($count) {
			
			# search GET|POST|PUT|DELETE|ANY /search/@keywords
            case 3:
                list($name, $method, $url) = $patterns;
				
				$methods = explode('|', $method);
				
                if (array_diff($methods, static::$allowedMethods)) {
                    throw new Exception('Method:' . $method . ' is not valid');
                }
				
				# 静态路由
				if (false === strpos($url, '{')) {
				    foreach($methods as $_method)
				    {
						static::$staticRoutes["$_method $url"] = $name;
				    }
				}

				static::addRoute($name, $url, $method, $widget, $action, $after, $vars, $request);

            break;
			
			# GET|POST|PUT|DELETE /search/@keywords
			# search /search/@keywords
            case 2:
			    list($search, $url) = $patterns;
				if(strpos($search, '|') || in_array($search, static::$allowedMethods)){
					$methods = explode('|', $search);
					
                    if (array_diff($methods, static::$allowedMethods)) {
                        throw new Exception('Method:' . $method . ' is not valid');
                    }
					
					# 没有路由标识  $method + $url 组合代替
				    foreach($methods as $method)
				    {
					    static::addRoute("$method $url", $url, $method, $widget, $action, $after, $vars, $request);
				    }
					
				    # 静态路由
				    if (false === strpos($url, '{')) {
				        foreach($methods as $method)
				        {
						    static::$staticRoutes["$method $url"] = "$method $url";
				        }
				    }
					
				} else {
					
					static::addRoute($search, $url, 'ANY', $widget, $action, $after, $vars, $request);
					
					# 静态路由
					false === strpos($url, '{') && static::$staticRoutes["ANY $url"] = $search;
				}
            break;
			
			# /search/@keywords
            default:

				static::addRoute("ANY $pattern", $pattern, 'ANY', $widget, $action, $after, $vars, $request);
				
				# 静态路由
				false === strpos($pattern, '{') && static::$staticRoutes["ANY $pattern"] = "ANY $pattern";
            break;
        }
    }

    /**
     * 注册路由规则        
     *  
     * @param string $name   路由标识
     * @param mixed  $url  路由地址
     * @param string $method 请求类型 
     * @param string $widget 组件名称
	 * @param string|null $action 组件动作
     * @param string|null $after 在某个路由后面
     * @return void
     */
    protected static function addRoute(string $name, $url, $method, $widget, ?string $action = null, ?string $after = null, array $vars = [], array $request = [])
    {	
		# 路由规则变量数组
        $args = [];

		# 解析路由
        if (false !== strpos($url, '{')) {

			# 路由地址格式
            $format = '';
			
			# 拆分路由
			#  '/admin/?{id:^[0-9]{4}(\-|\/)[0-9]{1,2}}/?{type:.*}'
			#  '/admin/?  id:^[0-9]  4}(\-|\/)[0-9]  1,2}}/?  type:.*}'
			
		    $urlParts = explode('{', $url);
			$temp = '';
			$close = 1;
			$route = '';
			
            foreach ($urlParts as $index => $urlPart) 
			{
				if(!$index) {
					if($urlPart){
				        $format .= $urlPart;
					    $route .= $urlPart;
					}
					continue;
				}

				$start = strpos($urlPart, '}');
				
				# 内嵌{}参数项 如：{id:^[0-9]{4}}
				if(false === $start){
					
					# 开始
					$close = 0;
				    $ids = strpos($urlPart, ':');
							
					if(false !== $ids){
								
					    $args[] = substr($urlPart, 0, $ids);
						$route .= '(' . substr($urlPart, $ids + 1);
						        
					} else {
							
					    $route .= '(' . $urlPart;
					}

				} else {

					
				    $end = strripos($urlPart, '}');	
					
					# 内嵌{}参数项 结束
				    if($end > $start){
						
						$temp = substr($urlPart, $end + 1);
						$format .= '%s' . $temp;
						$route .= '{' . substr($urlPart, 0, $end) . ')' . $temp;
						
						# 闭合
						$close = 1;

					} else {
						
						# 没有内嵌{}参数项
						if($close){

							$regex = substr($urlPart, 0, $start);
							$ids = strpos($regex, ':');
							
					        if(false !== $ids){
								
								$args[] = substr($regex, 0, $ids);
						        $regex = substr($regex, $ids + 1);
						        
					        }
							
							$temp = substr($urlPart, $start + 1);
							
							$format .= '%s' . $temp;
							$route .= '(' . $regex . ')' . $temp;
						
						} else {
							
							$route .= '{' . $urlPart;
						}
					}
				}
            }
        } else {
			
			# 路由地址格式
            $format = $url;
			$route = $url;
		}

		$rule = Config::make([

			# 路由标识
		    'name'=>$name,
		
		    # 路由地址格式
		    'format'=> str_replace('/?', '/', $format),
			
			# 路由规则变量
			'args'=>$args,
			
			# 组件动作
			'action'=>$action,
			
			# 请求类型
			'method'=>$method,
			
			# 路由地址
			'url'=>$url,
			
			# 路由地址
			'route'=>$route,
			
			# widget
			'widget'=>$widget,
			
			# 路由变量 []
			'vars'=>$vars,
			
			# 路由参数
			'params'=>$request
		]);
		
		if($after){
			
		    # 先清除原先实例
		    ($after != $name && isset(static::$rules[$name])) && static::clear($name);
			
            $pos = 0;
            foreach (static::$rules as $key => $val) 
			{
                $pos++;

                if ($key == $after) {
                    break;
                }
            }

            $pre = array_slice(static::$rules, 0, $pos);
            $next = array_slice(static::$rules, $pos);
            static::$rules = array_merge($pre, [$name => $rule], $next);

		}else{
			
			static::$rules[$name] = $rule;
		}
    }

    /**
     * 路由解析
     * @access public
	 * @param string $method   请求类型
     * @param string $uri   路由规则: /search/keywords
     * @return void 
     */
    public static function match(?string $method, ?string $uri)
    {
		$current = NULL;
		$rules = static::$rules;

		# 查找完全匹配项
        if (isset(static::$staticRoutes["$method $uri"]) && ($item = static::$staticRoutes["$method $uri"])
		    || isset(static::$staticRoutes["ANY $uri"]) && ($item = static::$staticRoutes["ANY $uri"])) {

			$current = $item;
			
        } else {


            $routeMap = $regexes = [];
            $numGroups = 0;

            /** @var Route $route */
            foreach ($rules as $rule) 
		    {
				# 跳过静态路由
				if(isset(static::$staticRoutes['ANY ' .$rule->url]) || isset(static::$staticRoutes["$method " .$rule->url])) {
					continue;
				}

                $numVariables = count($rule->args);
                $numGroups = max($numGroups, $numVariables);

                $regexes[] = $rule->route . str_repeat('()', $numGroups - $numVariables);
                $routeMap[$numGroups + 1] = $rule->name;

                ++$numGroups;
            }

            $regex = sprintf('~^(?|%s)$~', implode('|', $regexes));

            preg_match($regex, $uri, $matches);

            $cnt = count($matches);
            if (isset($routeMap[$cnt])) {
                $current = $routeMap[$cnt];
					
		        if(false === strpos($rules[$current]->method, 'ANY') && false === strpos($rules[$current]->method, $method)) {
			        $current = NULL;
		        }

                $params = $rules[$current]->args;

				array_shift($matches);

                foreach ($params as $k => $v) {
                    !empty($matches[$k]) && $rules[$current]->params[$v] = $matches[$k];
                }
            }
		}

		return static::$rules[$current] ?? false;
	}
	
    /**
     * 路由资源
     * @access public
	 * @param string $method   请求类型
     * @param string $uri   路由规则: /search/keywords
     * @return void 
     */
    public static function resource($uri, $method = 'ANY')
    {
		$rule = static::match($method, $uri);

		if ($rule) {

			return Base::widget($rule->widget, $rule->vars, $rule->params);

		}
		
		return false;
	}
	
    /**
     * 路由分发函数
     * @return bool
     */
    public static function dispatch()
    {
		$request = Request::instance();
		
		$rule = static::match($request->method(), $request->pathinfo());

		if ($rule) {
			
			static::$current = $rule;
			$widget = Base::widget($rule->widget, $rule->vars, $rule->params);

			# 执行函数
            if (isset($rule->action)) {
                $widget->{$rule->action}();
            }
			
		} else {
			throw new Exception('Page not found', 404);
		}
		
    }
	
    /**
     * 检查从指定的路由名称反向生成静态路径
     * 
     * @param string $name 路由名称
     * @param string $value 路由参数
     * @return string
     */
    public static function ruleUrl(string $name, ?array $value = NULL)
    {
		$route =  static::$rules[$name];

        # 交换数组键值
        $pattern = [];
        foreach ($route->args as $row) 
		{
            $pattern[$row] = isset($value[$row]) ? $value[$row] : '';
        }

        return '/' == $route->format ? '/' : rtrim(vsprintf($route->format, $pattern), '/');
    }
	
    /**
     * 检查从指定的路由名称反向生成静态链接
     * 
     * @param string $url 路由名称
     * @param string $params 路由参数
     * @param string $query 配置参数
     * @return string
     */
    public static function buildUrl($name, $parameters = [], $query = NULL)
    {
        $url = static::exists($name) 
		    ? static::$prefix . '/' . ltrim(static::ruleUrl($name, $parameters), '/')
			: '';
			
        if ($query && $url) {
            $parts = parse_url($url);
			
            /** 初始化参数 */
            if (is_string($query)) {
                parse_str($query, $args);
            } else {
                $args = $query;
            }
			
            /** 构造query */
            if (isset($parts['query'])) {
                parse_str($parts['query'], $currentArgs);
                $args = array_merge($currentArgs, $args);
            }
            $parts['query'] = http_build_query($args);
			
            /** 返回地址 */
            $url = Util::buildUrl($parts);
        }
        return $url;
    }
	
    /**
     * 导入解析后的路由规则
     *
     * @access public
     * @param mixed $rules 配置信息
     * @return void
     */
    public static function setRules($rules)
    {
        static::$rules = $rules;
    }
	
    /**
     * 删除路由规则
     *
     * @param string $name 路由名或者标识
     * @return void
     */
    public static function clear($name = NULL) 
	{
        if (is_null($name)) {
			
			static::$staticRoutes = [];
            static::$rules = [];
		    static::$current = '';
        } else {
			
			unset(static::$rules[$name]);
            foreach (static::$staticRoutes as $key => $val) {
                if ($name == $val) {
                    unset(static::$staticRoutes[$key]);
                }
            }
        }
    }
	
	/**
	 * 设置路由路径前缀
	 *
	 * @param string $name 配置参数名
	 * @return mixed
	 */
	public static function prefix(string $prefix)
	{
		static::$prefix = rtrim($prefix, '/');
	}
	
	/**
	 * 当前匹配的路由
	 *
	 * @param string $name 配置参数名
	 * @return mixed
	 */
	public static function current() 
	{
		return static::$current;
	}
	
    /**
     * 获取路由信息
     *
     * @access public
     * @param mixed $rules 配置信息
     * @return void
     */
    public static function get(string $index)
    {
		return static::$rules[$index] ?? Blank::instance();
    }
	
    /**
     * 检测路由信息是否存在
     * @access public
     * @param  string $name 路由名称
     * @return bool
     */
    public static function exists(string $name): bool
    {
        return isset(static::$rules[$name]);
    }

    /**
     * 输出所有配置项
     *
     * @return array
     */
    public static function export(): array
    {
		return static::$rules;
    }

}
